<template>
  <div ref="container">
    <div id="infoBox"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

export default {
  name: 'model',
  data() {
    return {
      //场景
      scene: null,
      //摄影机
      camera: null,
      //渲染器
      renderer: null,
      //相机控件
      controls: null
    }
  },
  mounted() {
    // 调用方法创建场景、相机、渲染器和相机控件
    this.createScene()
    this.createCamera()
    this.createRenderer()
    this.createControls()
    this.createMesh()
  },
  methods: {
    createMesh() {
      // 创建两个不同大小的立方体模型,材质使用不受光照影响的 MeshBasicMaterial 材质
      const cube1 = new THREE.Mesh(new THREE.BoxGeometry(3, 2, 1), new THREE.MeshPhongMaterial({ color: 0x00ff00 }))
      const cube2 = new THREE.Mesh(new THREE.BoxGeometry(1, 2, 3), new THREE.MeshPhongMaterial({ color: 0x00ff00 }))
      cube1.position.set(-2, 0, 0)
      cube2.position.set(2, 0, 0)
      this.scene.add(cube1, cube2)

      // 灯光
      const light = new THREE.HemisphereLight(0xffffff, 0x888888)
      light.position.set(0, 1, 0)
      this.scene.add(light)

      // 坐标轴辅助对象
      let axisHelper = new THREE.AxesHelper(250)
      this.scene.add(axisHelper)

      // 1.创建 Raycaster 实例
      const raycaster = new THREE.Raycaster()
      const pointer = new THREE.Vector2() //创建一个二维向量为后面 Raycaster 实例调用 .setFromCamera 方法做准备

      // 3.定义窗口触发 pointermove 事件所执行的回调函数 onPointerMove
      const infoBox = document.querySelector('#infoBox') //获取Dom元素
      const onPointerMove = event => {
        // 如果不使用箭头函数需要注意this指向问题
        // 修改 pointer 的值：将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
        pointer.x = (event.clientX / window.innerWidth) * 2 - 1
        pointer.y = -(event.clientY / window.innerHeight) * 2 + 1
        // 计算物体和射线的焦点
        // 方法 .intersectObjects ( objects : Array, recursive : Boolean, optionalTarget : Array ) : Array
        // 作用：检测所有在射线与这些物体之间，包括或不包括后代的相交部分。返回结果时，相交部分将按距离进行排序，最近的位于第一个），
        //      相交部分和.intersectObject所返回的格式是相同的。
        const intersects = raycaster.intersectObjects(this.scene.children) //返回和射线相交的一组物体，值为数组

        // 没有相交物体时
        if (intersects.length === 0) {
          console.log('隐藏', this.scene.children)

          // 恢复模型颜色
          for (var i in this.scene.children) {
            this.scene.children[i].traverse(obj => {
              if (obj.isMesh) {
                obj.material.color = new THREE.Color('rgb(0, 255, 0)')
              }
            })
          }

          // 隐藏提示框
          infoBox.style.display = 'none'
          return
        }

        // 有相交物体时
        if (intersects.length > 0) {
          if (intersects[0].object.isMesh) {
            console.log('显示')
            // 其中数组第一个值的 object属性值就是鼠标放在屏幕上离我们最近的模型
            // 改变颜色
            intersects[0].object.material.color.set(0xff0000)

            // 显示提示框信息
            infoBox.innerHTML = `长：${intersects[0].object.geometry.parameters.depth}
        <br>宽：${intersects[0].object.geometry.parameters.width}<br>
        高：${intersects[0].object.geometry.parameters.height}`
            infoBox.style.display = 'block'
            infoBox.style.left = event.clientX + 'px' //记得一定要拼接 px 我就是开始忘记了，导致信息框不移动
            infoBox.style.top = event.clientY + 'px'
          }
        }
      }

      // 2.为窗口绑定事件 pointermove 想使用点击事件 click 的可以自行修改
      window.addEventListener('pointermove', onPointerMove)

      const render = () => {
        // 4.通过摄像机和鼠标位置更新射线
        raycaster.setFromCamera(pointer, this.camera)
        // 手动更改相机的变换后，必须调用controls.update（）
        this.controls.update()
        this.renderer.render(this.scene, this.camera)
        requestAnimationFrame(render)
      }
      render()
    },

    // 创建场景
    createScene() {
      this.scene = new THREE.Scene()
    },

    // 创建相机
    createCamera() {
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
      this.camera.position.set(0, 0, 5)
    },

    // 创建渲染器
    createRenderer() {
      this.renderer = new THREE.WebGLRenderer({ antialias: true }) //antialias:是否执行抗锯齿,默认为false.
      this.renderer.setSize(window.innerWidth, window.innerHeight)
      this.$refs.container.appendChild(this.renderer.domElement)
    },

    // 创建相机控件
    createControls() {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    }
  }
}
</script>

<style scoped>
#infoBox {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  background-color: #fff;
  border: 1px solid #ccc;
  padding: 5px;
}
</style>
